#include <iostream>

// Irrlicht and Cg Headers
#include "irrlicht.h"
#include <Cg/cg.h>

// IrrCg
#include "IrrCg.h"

// Irrlicht namespaces
using namespace irr;

using namespace core;
using namespace scene;
using namespace video;

// Chernobyl Game Engine namespaces
using namespace IrrCg;

// Other namespaces
using namespace std;

// CallBack.

class ICGE_CGFX_CB : public ICgShaderConstantSetCallBack
{
public:
    ICGE_CGFX_CB() : pWVP(0), pSampler01(0), pSampler02(0)
    {
    }

    virtual void OnSetConstants(ICgServices* Services,const CGeffect& Effect,const CGpass& Pass,const CGprogram& Vertex,const CGprogram& Fragment,const SMaterial& Material)
    {
        //printf("%s\n", cgGetPassName(Pass));

        /* For optimization, You can set uniform variables, eg. setTexture, Matrix etc. only in important passes.
        You can use trick similar for this for them:
        if(Pass && (strcmp(cgGetPassName(Pass),"PASS0_NAME_FROM_CGFX_FILE") || strcmp(cgGetPassName(Pass),"PASS1_NAME_FROM_CGFX_FILE")))
        {
            // send variables here...
        }*/

        // Vertex.

        matrix4 Matrix;

        pWVP = cgGetNamedEffectParameter(Effect, "WVP");
        Matrix = Services->getDevice()->getVideoDriver()->getTransform(ETS_PROJECTION);
        Matrix *= Services->getDevice()->getVideoDriver()->getTransform(ETS_VIEW);
        Matrix *= Services->getDevice()->getVideoDriver()->getTransform(ETS_WORLD);
        cgSetMatrixParameterfr(pWVP, Matrix.pointer());

        // Fragment.

        pSampler01 = cgGetNamedEffectParameter(Effect, "Sampler01");
        Services->setTexture( pSampler01,Material.TextureLayer[0],0 ); // We can't use optimized EnableTexture function instead of setTexture in OpenGL mode, because next cgSetSamplerState need bind, active texture.
        cgSetSamplerState(pSampler01); // Important for apply sampler states from CgFX.

        pSampler02 = cgGetNamedEffectParameter(Effect, "Sampler02");
        Services->setTexture( pSampler02,Material.TextureLayer[1],1 );
        cgSetSamplerState(pSampler02);
    }

private:
    CGparameter	pWVP;

    CGparameter	pSampler01;
    CGparameter	pSampler02;
};

int main()
{
    printf("\n *** IrrCg CgFX Example by Patryk Nadrowski ***\n\n");

    stringw window = "";

    char i;

    // Select Irrlicht Driver
    E_DRIVER_TYPE DriverType;
	printf("\n Please select the driver you want for this demo:\n\n"\
		"  (a) Direct3D 9.0c\n  (b) OpenGL 1.5\n"\
		"  (otherKey) exit\n\n");

	std::cin >> i;

	switch(i)
	{
		case 'a':
            DriverType = EDT_DIRECT3D9;
            window = " Direct3D9 ";
            break;

		case 'b':
            DriverType = EDT_OPENGL;
            window = " OpenGL ";
            break;

		default:
            return 1;
	}

	IrrlichtDevice* Device = createDevice(DriverType,dimension2d<u32>(800 , 600),32,false,false,false,0);
	IVideoDriver* Driver = Device->getVideoDriver();
	ISceneManager* SceneManager = Device->getSceneManager();

	ICgProgrammingServices* GPU = new ICgProgrammingServices(Device, true); // Second parameter is important when we use CgFX.

	// Load textures.

	ITexture* Tex0 = Driver->getTexture("../../media/checker.bmp");
	ITexture* Tex1 = Driver->getTexture("../../media/test.bmp");
	ITexture* Tex2 = Driver->getTexture("../../media/wall.jpg");
	ITexture* Tex3 = Driver->getTexture("../../media/dwarf/axe.jpg");
	ITexture* Tex4 = Driver->getTexture("../../media/dwarf/dwarf.jpg");

    // Create Node 01 - With Cg shader.

    IMesh* Mesh01 = SceneManager->getMesh("../../media/elephant.obj");

    for(int i = 0; i < Mesh01->getMeshBufferCount(); i++)
    Mesh01->getMeshBuffer(i)->setHardwareMappingHint(EHM_STATIC);

	IMeshSceneNode* Node01 = SceneManager->addMeshSceneNode(Mesh01);
	Node01->setPosition(vector3df(-80,0,150));
	Node01->setRotation(vector3df(0,90,0));
	Node01->setScale(vector3df(1,1,1));
	Node01->setAutomaticCulling(EAC_FRUSTUM_BOX);
	Node01->setMaterialFlag(EMF_LIGHTING, false);

    Node01->getMaterial(0).TextureLayer[0].Texture = Tex0;
    Node01->getMaterial(0).TextureLayer[1].Texture = Tex1;

    // Create Node 02 - Without Cg shader.

    ISceneNode* Node02 = SceneManager->addCubeSceneNode(50);
	Node02->setPosition(vector3df(0,0,150));
	Node02->setRotation(vector3df(0,0,0));
	Node02->setScale(vector3df(1,1,1));
	Node02->setAutomaticCulling(EAC_FRUSTUM_BOX);
	Node02->setMaterialFlag(EMF_LIGHTING, false);
    Node02->getMaterial(0).TextureLayer[0].Texture = Tex2;

    // Create Animated Node 03 - With Cg shader.

    IAnimatedMesh* Mesh02 = SceneManager->getMesh("../../media/dwarf/dwarf.x");

    for(int i = 0; i < Mesh02->getMeshBufferCount(); i++)
    Mesh02->getMeshBuffer(i)->setHardwareMappingHint(EHM_STREAM);

	IAnimatedMeshSceneNode* Node03 = SceneManager->addAnimatedMeshSceneNode(Mesh02);
	Node03->setPosition(vector3df(70,-40,150));
	Node03->setRotation(vector3df(0,0,0));
	Node03->setScale(vector3df(1,1,1));
	Node03->setAutomaticCulling(EAC_FRUSTUM_BOX);
	Node03->setMaterialFlag(EMF_LIGHTING, false);

    Node03->getMaterial(0).TextureLayer[0].Texture = Tex3;
    Node03->getMaterial(0).TextureLayer[1].Texture = Tex1;
    Node03->getMaterial(1).TextureLayer[0].Texture = Tex4;
    Node03->getMaterial(1).TextureLayer[1].Texture = Tex0;

    // Create new 2 Materials from CgFX file.

    ICGE_CGFX_CB* CGFX_CB = new ICGE_CGFX_CB();

    s32 CGFX_MT0 = GPU->addCgEffectFromFiles("../../cgfx/05_Material_01.cgfx", CGFX_CB);
    s32 CGFX_MT1 = GPU->addCgEffectFromFiles("../../cgfx/05_Material_02.cgfx", CGFX_CB);

    // Apply materials to 2 Scene Nodes.

    Node01->setMaterialType((E_MATERIAL_TYPE)CGFX_MT0); // We can also apply other material type for each material, but we can only use effects with the same count of passes per Node (it will be improved when Irrlicht will be support disable material renderer function or similar).
    Node03->setMaterialType((E_MATERIAL_TYPE)CGFX_MT1);

    // Create special Multi Passes Nodes and assign it with Your Nodes.

    /* This is very important when You use CgFX material, if You need draw Node assigned to ICgSceneNode with standard material type
    eg. EMT_SOLID etc. or standard Cg, HLSL, GLSL, You mast call:
    Your_ICgSceneNode->setActive(false);
    and before draw with material type from CgFX You need:
    Your_ICgSceneNode->setActive(true);
    As You can see, ICgSceneNode with Active flag on True, You must use only on draw with material type from CgFX effect, as in this example. */


    ICgSceneNode* Node01_MP = new ICgSceneNode(Node01, SceneManager->getRootSceneNode(), SceneManager, -1);
    ICgSceneNode* Node03_MP = new ICgSceneNode(Node03, SceneManager->getRootSceneNode(), SceneManager, -1);

    // Create Camera

	ICameraSceneNode* Camera = SceneManager->addCameraSceneNodeFPS(0,100.0f,0.1f);
	Camera->setPosition(vector3df(0,0,0));
	Camera->setTarget(vector3df(0,0,150));

    // Last FPS
    int lastFPS = -1;

	while(Device->run())
    if (Device->isWindowActive())
	{
        // Begin Scene
		Driver->beginScene(true, true, SColor(255,213,255,112));

        SceneManager->drawAll();

        // End Scene
		Driver->endScene();

		int fps = Driver->getFPS();
		int poly = Driver->getPrimitiveCountDrawn();

		if (lastFPS != fps)
		{
		    stringw str = "";
            str = L"Irrlicht Cg - CgFX for";
            str += window;
            str += L" FPS: ";
            str += fps;
            str += L" Poly: ";
            str += poly;

            Device->setWindowCaption(str.c_str());
            lastFPS = fps;
		}
	}

    delete CGFX_CB;
	delete GPU;

    Device->drop();

	return 0;
}
